 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.update.internal.core;


 import java.io.IOException ;
 import java.io.InputStream ;
 import java.util.Iterator ;
 import java.util.Stack ;

 import javax.xml.parsers.ParserConfigurationException ;
 import javax.xml.parsers.SAXParser ;
 import javax.xml.parsers.SAXParserFactory ;

 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.update.core.IURLEntry;
 import org.eclipse.update.core.IUpdateConstants;
 import org.eclipse.update.core.model.ContentEntryModel;
 import org.eclipse.update.core.model.FeatureModel;
 import org.eclipse.update.core.model.FeatureModelFactory;
 import org.eclipse.update.core.model.ImportModel;
 import org.eclipse.update.core.model.IncludedFeatureReferenceModel;
 import org.eclipse.update.core.model.InstallHandlerEntryModel;
 import org.eclipse.update.core.model.NonPluginEntryModel;
 import org.eclipse.update.core.model.PluginEntryModel;
 import org.eclipse.update.core.model.URLEntryModel;
 import org.xml.sax.Attributes ;
 import org.xml.sax.InputSource ;
 import org.xml.sax.SAXException ;
 import org.xml.sax.SAXParseException ;
 import org.xml.sax.helpers.DefaultHandler ;

 /**
  * Default feature parser.
  * Parses the feature manifest file as defined by the platform. Defers
  * to a model factory to create the actual concrete model objects. The
  * update framework supplies two factory implementations:
  * <ul>
  * <p>
  * <b>Note:</b> This class/interface is part of an interim API that is still under development and expected to
  * change significantly before reaching stability. It is being made available at this early stage to solicit feedback
  * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken
  * (repeatedly) as the API evolves.
  * </p>
  * <li>@see org.eclipse.update.core.model.FeatureModelFactory
  * <li>@see org.eclipse.update.core.BaseFeatureFactory
  * </ul>
  *
  * @since 2.0
  */
 public class InternalFeatureParser extends DefaultHandler {

     private SAXParser parser;
     private FeatureModelFactory factory;
     private MultiStatus status;

     private boolean URL_ALREADY_SEEN = false;

     private static final int STATE_IGNORED_ELEMENT = -1;
     private static final int STATE_INITIAL = 0;
     private static final int STATE_INCLUDES = 1;
     private static final int STATE_FEATURE = 2;
     private static final int STATE_HANDLER = 3;
     private static final int STATE_DESCRIPTION = 4;
     private static final int STATE_COPYRIGHT = 5;
     private static final int STATE_LICENSE = 6;
     private static final int STATE_URL = 7;
     private static final int STATE_UPDATE = 8;
     private static final int STATE_DISCOVERY = 9;
     private static final int STATE_REQUIRES = 10;
     private static final int STATE_IMPORT = 11;
     private static final int STATE_PLUGIN = 12;
     private static final int STATE_DATA = 13;
     private static final String PLUGIN_ID = UpdateCore.getPlugin().getBundle().getSymbolicName();

     private static final String FEATURE = "feature"; //$NON-NLS-1$
 private static final String INCLUDES = "includes"; //$NON-NLS-1$
 private static final String HANDLER = "install-handler"; //$NON-NLS-1$
 private static final String DESCRIPTION = "description"; //$NON-NLS-1$
 private static final String COPYRIGHT = "copyright"; //$NON-NLS-1$
 private static final String LICENSE = "license"; //$NON-NLS-1$
 private static final String URL = "url"; //$NON-NLS-1$
 private static final String UPDATE = "update"; //$NON-NLS-1$
 private static final String DISCOVERY = "discovery"; //$NON-NLS-1$
 private static final String REQUIRES = "requires"; //$NON-NLS-1$
 private static final String IMPORT = "import"; //$NON-NLS-1$
 private static final String PLUGIN = "plugin"; //$NON-NLS-1$
 private static final String DATA = "data"; //$NON-NLS-1$
 // Current State Information
 Stack stateStack = new Stack ();

     // Current object stack (used to hold the current object we are
 // populating in this plugin descriptor
 Stack objectStack = new Stack ();

     private int currentState;
     private String location;
     
     private final static SAXParserFactory parserFactory =
         SAXParserFactory.newInstance();

     /**
      * Constructs a feature parser.
      *
      * @since 2.0
      */
     public InternalFeatureParser() {
         super();
         try {
             parserFactory.setNamespaceAware(true);
             this.parser = parserFactory.newSAXParser();
         } catch (ParserConfigurationException e) {
             UpdateCore.log(e);
         } catch (SAXException e) {
             UpdateCore.log(e);
         }
     }

     public void init(FeatureModelFactory factory) {
         init(factory, null);
     }
     
     /**
      * @param factory
      * @param location
      * @since 3.1
      */
     public void init(FeatureModelFactory factory, String location) {
         // PERF: separate instance creation from parsing
 this.factory = factory;
         stateStack = new Stack ();
         objectStack = new Stack ();
         status = null;
         URL_ALREADY_SEEN = false;
         this.location = location;
         //parser.reset();
 }
     
     public void internalInit(FeatureModelFactory factory, String location) {
         init(factory, location);
         stateStack.push(new Integer (STATE_INITIAL));
         currentState = ((Integer ) stateStack.peek()).intValue();
     }
     
     public FeatureModel getFeatureModel() throws SAXException {
         if (objectStack.isEmpty())
             throw new SAXException (Messages.DefaultFeatureParser_NoFeatureTag);
         else {
             if (objectStack.peek() instanceof FeatureModel) {
                 return (FeatureModel) objectStack.pop();
             } else {
                 String stack = ""; //$NON-NLS-1$
 Iterator iter = objectStack.iterator();
                 while (iter.hasNext()) {
                     stack = "\r\n" + iter.next().toString() + stack; //$NON-NLS-1$
 }
                 throw new SAXException (NLS.bind(Messages.DefaultFeatureParser_WrongParsingStack, (new String [] { stack })));
             }
         }
     }

     /**
      * Parses the specified input steam and constructs a feature model.
      * The input stream is not closed as part of this operation.
      *
      * @param in input stream
      * @return feature model
      * @exception SAXException
      * @exception IOException
      * @since 2.0
      */
     public FeatureModel parse(InputStream in) throws SAXException , IOException {
         stateStack.push(new Integer (STATE_INITIAL));
         currentState = ((Integer ) stateStack.peek()).intValue();
         parser.parse(new InputSource (in), this);
         return getFeatureModel();
     }

     /**
      * Returns all status objects accumulated by the parser.
      *
      * @return multi-status containing accumulated status, or <code>null</code>.
      * @since 2.0
      */
     public MultiStatus getStatus() {
         return status;
     }

     /**
      * Handle start of element tags
      * @see DefaultHandler#startElement(String, String, String, Attributes)
      * @since 2.0
      */
     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("Start Element: uri:" + uri + " local Name:" + localName + " qName:" + qName); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

         switch (currentState) {
             case STATE_IGNORED_ELEMENT :
                 internalErrorUnknownTag(NLS.bind(Messages.DefaultFeatureParser_UnknownElement, (new String [] { localName, getState(currentState) })));
                 break;

             case STATE_INITIAL :
                 handleInitialState(localName, attributes);
                 break;

             case STATE_FEATURE :
             case STATE_INCLUDES :
             case STATE_HANDLER :
             case STATE_DESCRIPTION :
             case STATE_COPYRIGHT :
             case STATE_LICENSE :
                 handleFeatureState(localName, attributes);
                 break;

             case STATE_URL :
                 if (URL_ALREADY_SEEN)
                     internalError(Messages.DefaultFeatureParser_TooManyURLtag);
                 handleURLState(localName, attributes);
                 break;

             case STATE_UPDATE :
             case STATE_DISCOVERY :
                 handleUpdateDiscoveryState(localName, attributes);
                 break;

             case STATE_REQUIRES :
                 handleRequiresState(localName, attributes);
                 break;

             case STATE_IMPORT :
                 handleImportState(localName,attributes);
                 break;
                 
             case STATE_PLUGIN :
             case STATE_DATA :
                 handleFeatureState(localName, attributes);
                 break;

             default :
                 internalErrorUnknownTag(NLS.bind(Messages.DefaultFeatureParser_UnknownStartState, (new String [] { Integer.toString(currentState) })));
                 break;
         }

         int newState = ((Integer ) stateStack.peek()).intValue();
         if (newState != STATE_IGNORED_ELEMENT)
             currentState = newState;

     }

     /**
      * Handle end of element tags
      * @see DefaultHandler#endElement(String, String, String)
      * @since 2.0
      */
     public void endElement(String uri, String localName, String qName) {

         // variables used
 URLEntryModel info = null;
         FeatureModel featureModel = null;
         String text = null;
         int innerState = 0;

         int state = ((Integer ) stateStack.peek()).intValue();
         switch (state) {
             case STATE_IGNORED_ELEMENT :
                 stateStack.pop();
                 break;

             case STATE_INITIAL :
                 internalError(Messages.DefaultFeatureParser_ParsingStackBackToInitialState);
                 break;

             case STATE_FEATURE :
                 stateStack.pop();
                 if (objectStack.peek() instanceof String ) {
                     text = (String ) objectStack.pop();
                     FeatureModel feature = (FeatureModel) objectStack.peek();
                     feature.getDescriptionModel().setAnnotation(text);
                 }
                 //do not pop
 break;

             case STATE_INCLUDES :
                 stateStack.pop();
                 if (objectStack.peek() instanceof IncludedFeatureReferenceModel) {
                     IncludedFeatureReferenceModel includedFeatureRefModel = ((IncludedFeatureReferenceModel) objectStack.pop());
                     if (objectStack.peek() instanceof FeatureModel) {
                         featureModel = (FeatureModel) objectStack.peek();
                         featureModel.addIncludedFeatureReferenceModel(includedFeatureRefModel);
                     }
                 }
                 break;

             case STATE_HANDLER :
                 stateStack.pop();
                 if (objectStack.peek() instanceof InstallHandlerEntryModel) {
                     InstallHandlerEntryModel handlerModel = (InstallHandlerEntryModel) objectStack.pop();
                     featureModel = (FeatureModel) objectStack.peek();
                     if (featureModel.getInstallHandlerModel() != null)
                         internalError(NLS.bind(Messages.DefaultFeatureParser_ElementAlreadySet, (new String [] { getState(state) })));
                     else
                         featureModel.setInstallHandlerModel(handlerModel);
                 }
                 break;

             case STATE_DESCRIPTION :
                 stateStack.pop();

                 text = ""; //$NON-NLS-1$
 while (objectStack.peek() instanceof String ) {
                     text = (String ) objectStack.pop() + text;
                 }
                 if (objectStack.peek() instanceof URLEntryModel) {
                     info = (URLEntryModel) objectStack.pop();
                     text = cleanupText(text);
                     if (text != null)
                         info.setAnnotation(text);

                     innerState = ((Integer ) stateStack.peek()).intValue();
                     switch (innerState) {
                         case STATE_FEATURE :
                             if (objectStack.peek() instanceof FeatureModel) {
                                 featureModel = (FeatureModel) objectStack.peek();
                                 if (featureModel.getDescriptionModel() != null)
                                     internalError(NLS.bind(Messages.DefaultFeatureParser_ElementAlreadySet, (new String [] { getState(state) })));
                                 else
                                     featureModel.setDescriptionModel(info);
                             }
                             break;

                         default :
                             internalError(NLS.bind(Messages.DefaultFeatureParser_StateIncludeWrongElement, (new String [] { getState(innerState), getState(state) })));
                             break;

                     }
                 }
                 break;

             case STATE_COPYRIGHT :
                 stateStack.pop();
                 text = ""; //$NON-NLS-1$
 while (objectStack.peek() instanceof String ) {
                     text = (String ) objectStack.pop() + text;
                 }
                 if (objectStack.peek() instanceof URLEntryModel) {
                     info = (URLEntryModel) objectStack.pop();
                     text = cleanupText(text);
                     if (text != null) {
                         info.setAnnotation(text);
                     }

                     innerState = ((Integer ) stateStack.peek()).intValue();
                     switch (innerState) {
                         case STATE_FEATURE :
                             if (objectStack.peek() instanceof FeatureModel) {
                                 featureModel = (FeatureModel) objectStack.peek();
                                 if (featureModel.getCopyrightModel() != null)
                                     internalError(NLS.bind(Messages.DefaultFeatureParser_ElementAlreadySet, (new String [] { getState(state) })));
                                 else
                                     featureModel.setCopyrightModel(info);
                             }
                             break;

                         default :
                             internalError(NLS.bind(Messages.DefaultFeatureParser_StateIncludeWrongElement, (new String [] { getState(innerState), getState(state) })));
                             break;

                     }
                 }
                 break;

             case STATE_LICENSE :
                 stateStack.pop();

                 text = ""; //$NON-NLS-1$
 while (objectStack.peek() instanceof String ) {
                     text = (String ) objectStack.pop() + text;
                 }
                 if (objectStack.peek() instanceof URLEntryModel) {
                     info = (URLEntryModel) objectStack.pop();
                     text = cleanupText(text);
                     if (text != null) {
                         info.setAnnotation(text);
                     }

                     innerState = ((Integer ) stateStack.peek()).intValue();
                     switch (innerState) {
                         case STATE_FEATURE :
                             if (objectStack.peek() instanceof FeatureModel) {
                                 featureModel = (FeatureModel) objectStack.peek();
                                 if (featureModel.getLicenseModel() != null)
                                     internalError(NLS.bind(Messages.DefaultFeatureParser_ElementAlreadySet, (new String [] { getState(state) })));
                                 else
                                     featureModel.setLicenseModel(info);
                             }
                             break;

                         default :
                             internalError(NLS.bind(Messages.DefaultFeatureParser_StateIncludeWrongElement, (new String [] { getState(innerState), getState(state) })));
                             break;

                     }
                 }
                 break;

             case STATE_URL :
                 stateStack.pop();
                 URL_ALREADY_SEEN = true;
                 break;

             case STATE_UPDATE :
                 stateStack.pop();
                 if (objectStack.peek() instanceof URLEntryModel) {
                     info = (URLEntryModel) objectStack.pop();
                     if (objectStack.peek() instanceof FeatureModel) {
                         featureModel = (FeatureModel) objectStack.peek();
                         if (featureModel.getUpdateSiteEntryModel() != null) {
                             internalError(NLS.bind(Messages.DefaultFeatureParser_ElementAlreadySet, (new String [] { getState(state) })));
                         } else {
                             featureModel.setUpdateSiteEntryModel(info);
                         }
                     }
                 }
                 break;

             case STATE_DISCOVERY :
                 stateStack.pop();
                 if (objectStack.peek() instanceof URLEntryModel) {
                     info = (URLEntryModel) objectStack.pop();
                     if (objectStack.peek() instanceof FeatureModel) {
                         featureModel = (FeatureModel) objectStack.peek();
                         featureModel.addDiscoverySiteEntryModel(info);
                     }
                 }
                 break;

             case STATE_REQUIRES :
                 stateStack.pop();
                 if (objectStack.peek() instanceof FeatureModel) {
                     featureModel = (FeatureModel) objectStack.peek();
                     ImportModel[] importModels = featureModel.getImportModels();
                     if (importModels.length == 0) {
                         internalError(Messages.DefaultFeatureParser_RequireStateWithoutImportElement);
                     } else {
                         boolean patchMode = false;
                         for (int i = 0; i < importModels.length; i++) {
                             ImportModel importModel = importModels[i];
                             if (importModel.isPatch()) {
                                 if (patchMode == false)
                                     patchMode = true;
                                 else {
                                     internalError(Messages.DefaultFeatureParser_MultiplePatchImports);
                                     break;
                                 }
                             }
                         }
                     }
                 }
                 break;

             case STATE_IMPORT :
                 stateStack.pop();
                 if (objectStack.peek() instanceof ImportModel) {
                     ImportModel importModel = (ImportModel) objectStack.pop();
                     if (objectStack.peek() instanceof FeatureModel) {
                         featureModel = (FeatureModel) objectStack.peek();
                         featureModel.addImportModel(importModel);
                     }
                 }
                 break;

             case STATE_PLUGIN :
                 stateStack.pop();
                 if (objectStack.peek() instanceof PluginEntryModel) {
                     PluginEntryModel pluginEntry = (PluginEntryModel) objectStack.pop();
                     if (objectStack.peek() instanceof FeatureModel) {
                         featureModel = (FeatureModel) objectStack.peek();
                         featureModel.addPluginEntryModel(pluginEntry);
                     }
                 }
                 break;

             case STATE_DATA :
                 stateStack.pop();
                 if (objectStack.peek() instanceof NonPluginEntryModel) {
                     NonPluginEntryModel nonPluginEntry = (NonPluginEntryModel) objectStack.pop();
                     if (objectStack.peek() instanceof FeatureModel) {
                         featureModel = (FeatureModel) objectStack.peek();
                         featureModel.addNonPluginEntryModel(nonPluginEntry);
                     }
                 }
                 break;

             default :
                 internalErrorUnknownTag(Messages.DefaultFeatureParser_UnknownEndState + state);
                 break;
         }

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("End Element:" + uri + ":" + localName + ":" + qName); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }
     
     /*
      * Method cleanupText.
      * Removes pre white space and post white space
      * return null if the text only contains whitespaces (\t \r\n and spaces)
      *
      * @param text or null
      * @return String
      */
     private String cleanupText(String text) {
         text = text.trim();
         if ("".equals(text)) return null; //$NON-NLS-1$
 return text;
     }

     /**
      * Handle character text
      * @see DefaultHandler#characters(char[], int, int)
      * @since 2.0
      */
     public void characters(char[] ch, int start, int length) {
         String text = ""; //$NON-NLS-1$
 boolean valid = true;

         if (valid) {
             text = new String (ch, start, length);
         }

         //only push if not unknown state
 int state = ((Integer ) stateStack.peek()).intValue();
         if (state == STATE_DESCRIPTION || state == STATE_COPYRIGHT || state == STATE_LICENSE)
             objectStack.push(text);

     }

     /**
      * Handle errors
      * @see DefaultHandler#error(SAXParseException)
      * @since 2.0
      */
     public void error(SAXParseException ex) {
         logStatus(ex);
     }

     /**
      * Handle fatal errors
      * @see DefaultHandler#fatalError(SAXParseException)
      * @exception SAXException
      * @since 2.0
      */
     public void fatalError(SAXParseException ex) throws SAXException {
         logStatus(ex);
         throw ex;
     }

     private void handleInitialState(String elementName, Attributes attributes) throws SAXException {
         if (elementName.equals(FEATURE)) {
             stateStack.push(new Integer (STATE_FEATURE));
             processFeature(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultFeatureParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
     }

     private void handleFeatureState(String elementName, Attributes attributes) throws SAXException {
         if (elementName.equals(HANDLER)) {
             stateStack.push(new Integer (STATE_HANDLER));
             processHandler(attributes);
         } else if (elementName.equals(DESCRIPTION)) {
             stateStack.push(new Integer (STATE_DESCRIPTION));
             processInfo(attributes);
         } else if (elementName.equals(COPYRIGHT)) {
             stateStack.push(new Integer (STATE_COPYRIGHT));
             processInfo(attributes);
         } else if (elementName.equals(LICENSE)) {
             stateStack.push(new Integer (STATE_LICENSE));
             processInfo(attributes);
         } else if (elementName.equals(URL)) {
             stateStack.push(new Integer (STATE_URL));
             //No process as URL tag does not contain any element itself
 } else if (elementName.equals(INCLUDES)) {
             stateStack.push(new Integer (STATE_INCLUDES));
             processIncludes(attributes);
         } else if (elementName.equals(REQUIRES)) {
             stateStack.push(new Integer (STATE_REQUIRES));
             processRequire(attributes);
         } else if (elementName.equals(PLUGIN)) {
             stateStack.push(new Integer (STATE_PLUGIN));
             processPlugin(attributes);
         } else if (elementName.equals(DATA)) {
             stateStack.push(new Integer (STATE_DATA));
             processData(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultFeatureParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
     }

     private void handleURLState(String elementName, Attributes attributes) throws SAXException {
         if (elementName.equals(UPDATE)) {
             stateStack.push(new Integer (STATE_UPDATE));
             processURLInfo(attributes);
         } else if (elementName.equals(DISCOVERY)) {
             stateStack.push(new Integer (STATE_DISCOVERY));
             processURLInfo(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultFeatureParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
     }

     private void handleRequiresState(String elementName, Attributes attributes) throws SAXException {
         if (elementName.equals(IMPORT)) {
             stateStack.push(new Integer (STATE_IMPORT));
             processImport(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultFeatureParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
     }
     private void handleUpdateDiscoveryState(String elementName, Attributes attributes) throws SAXException {
         if (elementName.equals(HANDLER)) {
             stateStack.push(new Integer (STATE_HANDLER));
             processHandler(attributes);
         } else if (elementName.equals(DESCRIPTION)) {
             stateStack.push(new Integer (STATE_DESCRIPTION));
             processInfo(attributes);
         } else if (elementName.equals(COPYRIGHT)) {
             stateStack.push(new Integer (STATE_COPYRIGHT));
             processInfo(attributes);
         } else if (elementName.equals(LICENSE)) {
             stateStack.push(new Integer (STATE_LICENSE));
             processInfo(attributes);
         } else if (elementName.equals(URL)) {
             stateStack.push(new Integer (STATE_URL));
             //No process as URL tag does not contain any element itself
 } else if (elementName.equals(INCLUDES)) {
             stateStack.push(new Integer (STATE_INCLUDES));
             processIncludes(attributes);
         } else if (elementName.equals(REQUIRES)) {
             stateStack.push(new Integer (STATE_REQUIRES));
             processRequire(attributes);
         } else if (elementName.equals(PLUGIN)) {
             stateStack.push(new Integer (STATE_PLUGIN));
             processPlugin(attributes);
         } else if (elementName.equals(DATA)) {
             stateStack.push(new Integer (STATE_DATA));
             processData(attributes);
         } else if (elementName.equals(UPDATE)) {
             stateStack.push(new Integer (STATE_UPDATE));
             processURLInfo(attributes);
         } else if (elementName.equals(DISCOVERY)) {
             stateStack.push(new Integer (STATE_DISCOVERY));
             processURLInfo(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultFeatureParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
         }



     private void handleImportState(String elementName, Attributes attributes) throws SAXException {
         if (elementName.equals(HANDLER)) {
             stateStack.push(new Integer (STATE_HANDLER));
             processHandler(attributes);
         } else if (elementName.equals(DESCRIPTION)) {
             stateStack.push(new Integer (STATE_DESCRIPTION));
             processInfo(attributes);
         } else if (elementName.equals(COPYRIGHT)) {
             stateStack.push(new Integer (STATE_COPYRIGHT));
             processInfo(attributes);
         } else if (elementName.equals(LICENSE)) {
             stateStack.push(new Integer (STATE_LICENSE));
             processInfo(attributes);
         } else if (elementName.equals(URL)) {
             stateStack.push(new Integer (STATE_URL));
             //No process as URL tag does not contain any element itself
 } else if (elementName.equals(INCLUDES)) {
             stateStack.push(new Integer (STATE_INCLUDES));
             processIncludes(attributes);
         } else if (elementName.equals(REQUIRES)) {
             stateStack.push(new Integer (STATE_REQUIRES));
             processRequire(attributes);
         } else if (elementName.equals(PLUGIN)) {
             stateStack.push(new Integer (STATE_PLUGIN));
             processPlugin(attributes);
         } else if (elementName.equals(DATA)) {
             stateStack.push(new Integer (STATE_DATA));
             processData(attributes);
         } else if (elementName.equals(IMPORT)) {
             stateStack.push(new Integer (STATE_IMPORT));
             processImport(attributes);
         } else
             internalErrorUnknownTag(NLS.bind(Messages.DefaultFeatureParser_UnknownElement, (new String [] { elementName, getState(currentState) })));
     }

     /*
      * Process feature information
      */
     private void processFeature(Attributes attributes) {

         // identifier and version
 String id = attributes.getValue("id"); //$NON-NLS-1$
 String ver = attributes.getValue("version"); //$NON-NLS-1$

         if (id == null || id.trim().equals("") //$NON-NLS-1$
 || ver == null || ver.trim().equals("")) { //$NON-NLS-1$
 internalError(NLS.bind(Messages.DefaultFeatureParser_IdOrVersionInvalid, (new String [] { id, ver, getState(currentState)})));
         } else {
             // create feature model
 FeatureModel feature = factory.createFeatureModel();

             feature.setFeatureIdentifier(id);
             feature.setFeatureVersion(ver);

             // label
 String label = attributes.getValue("label"); //$NON-NLS-1$
 feature.setLabel(label);

             // provider
 String provider = attributes.getValue("provider-name"); //$NON-NLS-1$
 feature.setProvider(provider);

             //image
 String imageURL = attributes.getValue("image"); //$NON-NLS-1$
 feature.setImageURLString(imageURL);

             // OS
 String os = attributes.getValue("os"); //$NON-NLS-1$
 feature.setOS(os);

             // WS
 String ws = attributes.getValue("ws"); //$NON-NLS-1$
 feature.setWS(ws);

             // NL
 String nl = attributes.getValue("nl"); //$NON-NLS-1$
 feature.setNL(nl);

             // arch
 String arch = attributes.getValue("arch"); //$NON-NLS-1$
 feature.setArch(arch);

             // primary
 String primary = attributes.getValue("primary"); //$NON-NLS-1$
 feature.setPrimary(primary != null && primary.trim().equalsIgnoreCase("true")); //$NON-NLS-1$

             // exclusive
 String exclusive = attributes.getValue("exclusive"); //$NON-NLS-1$
 feature.setExclusive(exclusive != null && exclusive.trim().equalsIgnoreCase("true")); //$NON-NLS-1$

             // application
 String application = attributes.getValue("application"); //$NON-NLS-1$
 feature.setApplication(application);

             // affinity
 String affinity = attributes.getValue("colocation-affinity"); //$NON-NLS-1$
 feature.setAffinityFeature(affinity);

             // primary plugin
 String plugin = attributes.getValue("plugin"); //$NON-NLS-1$
 feature.setPrimaryPluginID(plugin);

             objectStack.push(feature);

             if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING) {
                 debug("End process DefaultFeature tag: id:" //$NON-NLS-1$
 +id + " ver:" //$NON-NLS-1$
 +ver + " label:" //$NON-NLS-1$
 +label + " provider:" //$NON-NLS-1$
 +provider);
                 debug("End process DefaultFeature tag: image:" + imageURL); //$NON-NLS-1$
 debug("End process DefaultFeature tag: ws:" //$NON-NLS-1$
 +ws + " os:" //$NON-NLS-1$
 +os + " nl:" //$NON-NLS-1$
 +nl + " application:" //$NON-NLS-1$
 +application);
             }
         }
     }

     /*
      * process URL info with element text
      */
     private void processHandler(Attributes attributes) {
         InstallHandlerEntryModel handler = factory.createInstallHandlerEntryModel();

         String handlerURL = attributes.getValue("url"); //$NON-NLS-1$
 handler.setURLString(handlerURL);

         String library = attributes.getValue("library"); //$NON-NLS-1$
 handler.setLibrary(library);

         String clazz = attributes.getValue("handler"); //$NON-NLS-1$
 handler.setHandlerName(clazz);

         objectStack.push(handler);

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("Processed Handler: url:" //$NON-NLS-1$
 +handlerURL + " library:" //$NON-NLS-1$
 +library + " class:" //$NON-NLS-1$
 +clazz);
     }

     /*
      * process URL info with element text
      */
     private void processInfo(Attributes attributes) {
         URLEntryModel inf = factory.createURLEntryModel();
         String infoURL = attributes.getValue("url"); //$NON-NLS-1$
 inf.setURLString(infoURL);

         objectStack.push(inf);

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("Processed Info: url:" + infoURL); //$NON-NLS-1$
 }

     /*
      * Process includes information
      */
     private void processIncludes(Attributes attributes) {

         // identifier and version
 String id = attributes.getValue("id"); //$NON-NLS-1$
 String ver = attributes.getValue("version"); //$NON-NLS-1$

         if (id == null || id.trim().equals("") //$NON-NLS-1$
 || ver == null || ver.trim().equals("")) { //$NON-NLS-1$
 internalError(NLS.bind(Messages.DefaultFeatureParser_IdOrVersionInvalid, (new String [] { id, ver, getState(currentState)})));
         }

         IncludedFeatureReferenceModel includedFeature = factory.createIncludedFeatureReferenceModel();
         includedFeature.setFeatureIdentifier(id);
         includedFeature.setFeatureVersion(ver);

         // name
 String name = attributes.getValue("name");//$NON-NLS-1$
 includedFeature.setLabel(name);

         // optional
 String optional = attributes.getValue("optional");//$NON-NLS-1$
 boolean isOptional = "true".equalsIgnoreCase(optional);//$NON-NLS-1$
 includedFeature.isOptional(isOptional);

         // search location
 String locationName = attributes.getValue("search-location");//$NON-NLS-1$
 // bug 27030
 if (locationName == null)
             locationName = attributes.getValue("search_location");//$NON-NLS-1$
 int searchLocation = IUpdateConstants.SEARCH_ROOT;
         if ("both".equalsIgnoreCase(locationName))//$NON-NLS-1$
 searchLocation = IUpdateConstants.SEARCH_ROOT & IUpdateConstants.SEARCH_SELF;
         if ("self".equalsIgnoreCase(locationName))//$NON-NLS-1$
 searchLocation = IUpdateConstants.SEARCH_SELF;
         includedFeature.setSearchLocation(searchLocation);

         // os arch ws nl
 String os = attributes.getValue("os");//$NON-NLS-1$
 includedFeature.setOS(os);

         String ws = attributes.getValue("ws");//$NON-NLS-1$
 includedFeature.setWS(ws);

         String arch = attributes.getValue("arch");//$NON-NLS-1$
 includedFeature.setArch(arch);
         
         // NL
 String nl = attributes.getValue("nl"); //$NON-NLS-1$
 includedFeature.setNL(nl);

         objectStack.push(includedFeature);

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING) {
             debug("End process Includes tag: id:" //$NON-NLS-1$
 +id + " ver:" + ver); //$NON-NLS-1$
 debug("name =" + name + " optional=" + optional + " search-location=" + locationName); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 debug("os=" + os + " ws=" + ws + " arch=" + arch); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }
     }

     /*
      * process URL info with label attribute
      */
     private void processURLInfo(Attributes attributes) {
         URLEntryModel inf = factory.createURLEntryModel();
         String infoURL = attributes.getValue("url"); //$NON-NLS-1$
 String label = attributes.getValue("label"); //$NON-NLS-1$
 String type = attributes.getValue("type"); //$NON-NLS-1$
 inf.setURLString(infoURL);
         inf.setAnnotation(label);
         
         if ("web".equalsIgnoreCase(type)) //$NON-NLS-1$
 inf.setType(IURLEntry.WEB_SITE);
         else
             inf.setType(IURLEntry.UPDATE_SITE);

         if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
             debug("Processed URLInfo: url:" + infoURL + " label:" + label+" type:"+type);//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

         objectStack.push(inf);
     }

     /*
      * process import info
      */
     private void processImport(Attributes attributes) {
         String pluginID = attributes.getValue("plugin"); //$NON-NLS-1$
 String featureID = attributes.getValue("feature"); //$NON-NLS-1$
 String idMatch = attributes.getValue("id-match"); //$NON-NLS-1$

         if (!(pluginID == null ^ featureID == null)) {
             internalError(Messages.DefaultFeatureParser_PluginAndFeatureId);
             return;
         }

         // since 2.0.2 , manage feature and plugin import
 String id = null;
         if (pluginID == null) {
             id = featureID;
         } else {
             id = pluginID;
         }

         if (id == null || id.trim().equals("")) //$NON-NLS-1$
 internalError(NLS.bind(Messages.DefaultFeatureParser_MissingId, (new String [] { getState(currentState) })));
         else {
             ImportModel imp = factory.createImportModel();
             String ver = attributes.getValue("version"); //$NON-NLS-1$
 String match = attributes.getValue("match"); //$NON-NLS-1$
 String patch = attributes.getValue("patch"); //$NON-NLS-1$

             imp.setPatch(patch != null && patch.equalsIgnoreCase("true")); //$NON-NLS-1$

             if (ver == null) {
                 if (imp.isPatch()) {
                     internalError(Messages.DefaultFeatureParser_MissingPatchVersion);
                 }
                 ver = "0.0.0"; //$NON-NLS-1$
 match = "greaterOrEqual"; //$NON-NLS-1$
 } else if (match == null) {
                 if (imp.isPatch())
                     match = "perfect"; //$NON-NLS-1$
 else
                     match = "compatible"; //$NON-NLS-1$
 }

             imp.setIdentifier(id);
             imp.setVersion(ver);
             imp.setFeatureImport(featureID != null);
             imp.setMatchingRuleName(match);
             imp.setMatchingIdRuleName(idMatch);

             if (imp.isPatch()) {
                 // patch reference must be perfect.
 if (match != null && !match.equalsIgnoreCase("perfect")) { //$NON-NLS-1$
 internalError(Messages.DefaultFeatureParser_wrongMatchForPatch);
                 }
                 if (imp.isFeatureImport() == false) {
                     imp.setPatch(false);
                     internalError(Messages.DefaultFeatureParser_patchWithPlugin);
                 }
             }

             // os arch ws
 String os = attributes.getValue("os"); //$NON-NLS-1$
 imp.setOS(os);

             String ws = attributes.getValue("ws"); //$NON-NLS-1$
 imp.setWS(ws);

             String arch = attributes.getValue("arch"); //$NON-NLS-1$
 imp.setOSArch(arch);

             objectStack.push(imp);

             if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING) {
                 debug("Processed import: id:" + id + " ver:" + ver);//$NON-NLS-1$ //$NON-NLS-2$
 debug("Processed import: match:" + match); //$NON-NLS-1$
 }

         }
     }

     /*
      * process import info
      */
     private void processRequire(Attributes attributes) {
     }

     /*
     * process plugin entry info
     */
    private void processPlugin(Attributes attributes) {
        String id = attributes.getValue("id"); //$NON-NLS-1$
 String ver = attributes.getValue("version"); //$NON-NLS-1$
 if (id == null || id.trim().equals("") //$NON-NLS-1$
 || ver == null || ver.trim().equals("")) { //$NON-NLS-1$
 internalError(NLS.bind(Messages.DefaultFeatureParser_IdOrVersionInvalid, (new String [] { id, ver, getState(currentState)})));
        } else {
            PluginEntryModel pluginEntry = factory.createPluginEntryModel();
            pluginEntry.setPluginIdentifier(id);
            pluginEntry.setPluginVersion(ver);

            String fragment = attributes.getValue("fragment"); //$NON-NLS-1$
 pluginEntry.isFragment(fragment != null && fragment.trim().equalsIgnoreCase("true"));//$NON-NLS-1$

            //setOS
 String os = attributes.getValue("os"); //$NON-NLS-1$
 pluginEntry.setOS(os);

            //setWS
 String ws = attributes.getValue("ws"); //$NON-NLS-1$
 pluginEntry.setWS(ws);

            //setNL
 String nl = attributes.getValue("nl"); //$NON-NLS-1$
 pluginEntry.setNL(nl);

            // setArch
 String arch = attributes.getValue("arch"); //$NON-NLS-1$
 pluginEntry.setArch(arch);

            // setUnpack
 String unpack = attributes.getValue("unpack"); //$NON-NLS-1$
 pluginEntry.setUnpack(!"false".equalsIgnoreCase(unpack)); //$NON-NLS-1$

            // download size
 long download_size = ContentEntryModel.UNKNOWN_SIZE;
            String download = attributes.getValue("download-size"); //$NON-NLS-1$
 if (download != null && !download.trim().equals("")) { //$NON-NLS-1$
 try {
                    download_size = Long.valueOf(download).longValue();
                } catch (NumberFormatException e) {
                    // use UNKNOWN_SIZE
 }
            }
            pluginEntry.setDownloadSize(download_size);

            // install size
 long install_size = ContentEntryModel.UNKNOWN_SIZE;
            String install = attributes.getValue("install-size"); //$NON-NLS-1$
 if (install != null && !install.trim().equals("")) { //$NON-NLS-1$
 try {
                    install_size = Long.valueOf(install).longValue();
                } catch (NumberFormatException e) {
                    // use UNKNOWN_SIZE
 }
            }
            pluginEntry.setInstallSize(install_size);

            objectStack.push(pluginEntry);

            if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING) {
                debug("Processed Plugin: id:" + id + " ver:" + ver + " fragment:" + fragment); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 debug("Processed Plugin: os:" + os + " ws:" + ws + " nl:" + nl); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 debug("Processed Plugin: download size:" //$NON-NLS-1$
 +download_size + " install size:" //$NON-NLS-1$
 +install_size);
            }

        }
    }

    /*
     * process non-plug-in entry info
     */
    private void processData(Attributes attributes) {
        String id = attributes.getValue("id"); //$NON-NLS-1$
 if (id == null || id.trim().equals("")) { //$NON-NLS-1$
 internalError(NLS.bind(Messages.DefaultFeatureParser_MissingId, (new String [] { getState(currentState) })));
        } else {
            NonPluginEntryModel dataEntry = factory.createNonPluginEntryModel();
            dataEntry.setIdentifier(id);

            //setOS
 String os = attributes.getValue("os"); //$NON-NLS-1$
 dataEntry.setOS(os);

            //setWS
 String ws = attributes.getValue("ws"); //$NON-NLS-1$
 dataEntry.setWS(ws);

            //setNL
 String nl = attributes.getValue("nl"); //$NON-NLS-1$
 dataEntry.setNL(nl);

            // setArch
 String arch = attributes.getValue("arch"); //$NON-NLS-1$
 dataEntry.setArch(arch);

            // download size
 long download_size = ContentEntryModel.UNKNOWN_SIZE;
            String download = attributes.getValue("download-size"); //$NON-NLS-1$
 if (download != null && !download.trim().equals("")) { //$NON-NLS-1$
 try {
                    download_size = Long.valueOf(download).longValue();
                } catch (NumberFormatException e) {
                    // use UNKNOWN_SIZE
 }
            }
            dataEntry.setDownloadSize(download_size);

            // install size
 long install_size = ContentEntryModel.UNKNOWN_SIZE;
            String install = attributes.getValue("install-size"); //$NON-NLS-1$
 if (install != null && !install.trim().equals("")) { //$NON-NLS-1$
 try {
                    install_size = Long.valueOf(install).longValue();
                } catch (NumberFormatException e) {
                    // use UNKNOWN_SIZE
 }
            }
            dataEntry.setInstallSize(install_size);

            objectStack.push(dataEntry);

            if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING) {
                debug("Processed Data: id:" + id); //$NON-NLS-1$
 debug("Processed Data: download size:" //$NON-NLS-1$
 +download_size + " install size:" //$NON-NLS-1$
 +install_size);
            }

        }
    }

    private void debug(String s) {
        UpdateCore.debug("InternalFeatureParser: " + s); //$NON-NLS-1$
 }

    private void logStatus(SAXParseException ex) {
        String name = ex.getSystemId();
        if (name == null)
            name = ""; //$NON-NLS-1$
 else
            name = name.substring(1 + name.lastIndexOf("/")); //$NON-NLS-1$

        String msg;
        if (name.equals("")) { //$NON-NLS-1$
 msg = NLS.bind(Messages.DefaultFeatureParser_ErrorParsing, (new String [] { ex.getMessage() }));
        } else {
            String [] values = new String [] { name, Integer.toString(ex.getLineNumber()), Integer.toString(ex.getColumnNumber()), ex.getMessage()};
            msg = NLS.bind(Messages.DefaultFeatureParser_ErrorlineColumnMessage, values);
        }
        error(new Status(IStatus.ERROR, PLUGIN_ID, Platform.PARSE_PROBLEM, msg, ex));
    }

    /*
     * Handles an error state specified by the status. The collection of all logged status
     * objects can be accessed using <code>getStatus()</code>.
     *
     * @param error a status detailing the error condition
     */
    private void error(IStatus error) {

        if (status == null) {
            status = new MultiStatus(PLUGIN_ID, Platform.PARSE_PROBLEM, Messages.DefaultFeatureParser_ErrorParsingFeature, null);
        }

        status.add(error);
        if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_PARSING)
            UpdateCore.log(error);
    }

    private void internalErrorUnknownTag(String msg) {
        stateStack.push(new Integer (STATE_IGNORED_ELEMENT));
        internalError(msg);
    }

    private void internalError(String message) {
        if (location != null)
            message += " " + NLS.bind(Messages.DefaultFeatureParser_location, (new String [] { location })); //$NON-NLS-1$
 error(new Status(IStatus.ERROR, PLUGIN_ID, Platform.PARSE_PROBLEM, message, null));
    }

    /*
     * return the state as String
     */
    private String getState(int state) {

        switch (state) {
            case STATE_IGNORED_ELEMENT :
                return "Ignored"; //$NON-NLS-1$

            case STATE_INITIAL :
                return "Initial"; //$NON-NLS-1$

            case STATE_FEATURE :
                return "Feature"; //$NON-NLS-1$

            case STATE_HANDLER :
                return "Install Handler"; //$NON-NLS-1$

            case STATE_DESCRIPTION :
                return "description"; //$NON-NLS-1$

            case STATE_INCLUDES :
                return "includes"; //$NON-NLS-1$

            case STATE_COPYRIGHT :
                return "Copyright"; //$NON-NLS-1$

            case STATE_LICENSE :
                return "License"; //$NON-NLS-1$

            case STATE_URL :
                return "URL"; //$NON-NLS-1$

            case STATE_UPDATE :
                return "Update URL"; //$NON-NLS-1$

            case STATE_DISCOVERY :
                return "Discovery URL"; //$NON-NLS-1$

            case STATE_REQUIRES :
                return "Require"; //$NON-NLS-1$

            case STATE_IMPORT :
                return "Import"; //$NON-NLS-1$

            case STATE_PLUGIN :
                return "Plugin"; //$NON-NLS-1$

            case STATE_DATA :
                return "Data"; //$NON-NLS-1$

            default :
                return NLS.bind(Messages.DefaultFeatureParser_UnknownState, (new String [] { Integer.toString(state) }));
        }

    }

    /**
     * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
     */
    public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
        super.ignorableWhitespace(arg0, arg1, arg2);
    }
}

